#include "skynet.h"

#include "skynet_imp.h"
#include "skynet_env.h"
#include "skynet_server.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include <signal.h>
#include <assert.h>

static int
optint(const char *key, int opt) {
	const char * str = skynet_getenv(key);
	if (str == NULL) {
		char tmp[20];
		sprintf(tmp,"%d",opt);
		skynet_setenv(key, tmp);
		return opt;
	}
	return strtol(str, NULL, 10);
}

static int
optboolean(const char *key, int opt) {
	const char * str = skynet_getenv(key);
	if (str == NULL) {
		skynet_setenv(key, opt ? "true" : "false");
		return opt;
	}
	return strcmp(str,"true")==0;
}

static const char *
optstring(const char *key,const char * opt) {
	const char * str = skynet_getenv(key);
	if (str == NULL) {
		if (opt) {
			skynet_setenv(key, opt);
			opt = skynet_getenv(key);
		}
		return opt;
	}
	return str;
}

static void
_init_env(lua_State *L) {
	lua_pushnil(L);  /* first key 将一个nil值压入L的堆栈中*/
	while (lua_next(L, -2) != 0) {
		int keyt = lua_type(L, -2);
		if (keyt != LUA_TSTRING) {
			fprintf(stderr, "Invalid config table\n");
			exit(1);
		}

		const char *key = lua_tostring(L,-2); //取出key值

		//判断lua堆栈中自顶向下的第一个元素, 即value是否为boolean
		if (lua_type(L, -1) == LUA_TBOOLEAN) {
			int b = lua_toboolean(L,-1); //将value转换为C boolean值
			skynet_setenv(key, b ? "true" : "false" );
		} else {
			const char *value = lua_tostring(L,-1); //将value转换为C string值
			if (value == NULL) {
				fprintf(stderr, "Invalid config table key = %s\n", key);
				exit(1);
			}
			skynet_setenv(key, value);
		}
		lua_pop(L,1); //弹出目前位于栈顶的value
	}
	lua_pop(L,1);
}

//使skynet进程忽略SIGPIPE信号
int sigign() {
	struct sigaction sa;
	sa.sa_handler = SIG_IGN; //接收到指定信号时, 忽略它, 不做任何处理
	sa.sa_flags = 0; //信号处理依照默认行为
	sigemptyset(&sa.sa_mask); //在处理指定信号时, 不忽略接收到的任何其他的信号

	sigaction(SIGPIPE, &sa, 0); //修改SIGPIPE信号关联的处理方式
	return 0;
}

static const char * load_config = "\
	local result = {}\n\
	local function getenv(name) return assert(os.getenv(name), [[os.getenv() failed: ]] .. name) end\n\
	local sep = package.config:sub(1,1)\n\
	local current_path = [[.]]..sep\n\
	local function include(filename)\n\
		local last_path = current_path\n\
		local path, name = filename:match([[(.*]]..sep..[[)(.*)$]])\n\
		if path then\n\
			if path:sub(1,1) == sep then	-- root\n\
				current_path = path\n\
			else\n\
				current_path = current_path .. path\n\
			end\n\
		else\n\
			name = filename\n\
		end\n\
		local f = assert(io.open(current_path .. name))\n\
		local code = assert(f:read [[*a]])\n\
		code = string.gsub(code, [[%$([%w_%d]+)]], getenv)\n\
		f:close()\n\
		assert(load(code,[[@]]..filename,[[t]],result))()\n\
		current_path = last_path\n\
	end\n\
	setmetatable(result, { __index = { include = include } })\n\
	local config_name = ...\n\
	include(config_name)\n\
	setmetatable(result, nil)\n\
	return result\n\
";

int
main(int argc, char *argv[]) {
	const char * config_file = NULL ;
	if (argc > 1) {
		config_file = argv[1];
	} else {
		fprintf(stderr, "Need a config file. Please read skynet wiki : https://github.com/cloudwu/skynet/wiki/Config\n"
			"usage: skynet configfilename\n");
		return 1;
	}

	skynet_globalinit();
	skynet_env_init();

	sigign();

	struct skynet_config config;

#ifdef LUA_CACHELIB
	// init the lock of code cache (代码缓存, 里面也有一个lua状态机)
	luaL_initcodecache();
#endif

	//创建一个lua虚拟机L
	struct lua_State *L = luaL_newstate();
	luaL_openlibs(L);	// link lua lib

	//将load_config编译为一个lua函数并压入lua堆栈的栈顶
	int err =  luaL_loadbufferx(L, load_config, strlen(load_config), "=[skynet config]", "t");
	assert(err == LUA_OK);

	//将config_file作为lua字符串压入堆栈
	lua_pushstring(L, config_file);
	//以config_file作为参数, 执行load_config指代的lua代码, 最终, 代码块return的result会被作为lua table留在lua堆栈栈顶
	err = lua_pcall(L, 1, 1, 0);
	if (err) {
		fprintf(stderr,"%s\n",lua_tostring(L,-1));
		lua_close(L);
		return 1;
	}

	//从L的lua堆栈的table result中取出所有key-value, 加载为skynet_env *E的lua虚拟机中的全局变量
	_init_env(L);

	//optstring(key, 默认值), 这几个配置变量都以配置文件中key对应的内容为优先, 如果配置文件中没有, 则取默认值设置进环境变量中并返回。
	config.thread =  optint("thread", 8);
	config.module_path = optstring("cpath","./cservice/?.so");
	config.harbor = optint("harbor", 1);
	config.bootstrap = optstring("bootstrap","snlua bootstrap");
	config.daemon = optstring("daemon", NULL); //作为后台守护进程运行时，需要提供一个文件名
	config.logger = optstring("logger", NULL); //日志文件名
	config.logservice = optstring("logservice", "logger"); //日志模块.so
	config.profile = optboolean("profile", 1); //开关，统计每个服务使用了多少CPU时间，可以关闭

	lua_close(L); //使用完后释放L

	skynet_start(&config);
	skynet_globalexit();

	return 0;
}